﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VCDIFF.Commands;
using VCDIFF.Tools;
using System.IO;

namespace VCDIFF.Visit
{
    public class Writer : Visitor
    {
        public IList<byte> HeaderList { get; set; }
        public IList<byte> WindowList { get; set; }
        public IList<byte> AddAndRunDataSection { get; set; }
        public IList<byte> InstructionsAndSizesSection { get; set; }
        public IList<byte> CopyAddressesSection { get; set; }

        /// <summary>
        /// Writer's constructor.
        /// </summary>
        public Writer()
        {
            HeaderList = new List<byte>();
            WindowList = new List<byte>();
            AddAndRunDataSection = new List<byte>();
            InstructionsAndSizesSection = new List<byte>();
            CopyAddressesSection = new List<byte>();
        }

        /// <summary>
        /// Visits VCDIFF File.
        /// </summary>
        /// <param name="file">VCDIFF File to visit.</param>
        public override void Visit(VCDFile file)
        {
            file.Header.Write(this);
            foreach (Window window in file.WindowsList)
                window.Write(this);
        }

        /// <summary>
        /// Visits VCDIFF Header.
        /// </summary>
        /// <param name="header">VCDIFF Header to visit.</param>
        public override void Visit(Header header)
        {
            HeaderList.Add(header.Header1);
            HeaderList.Add(header.Header2);
            HeaderList.Add(header.Header3);
            HeaderList.Add(header.Header4);
            HeaderList.Add(header.HdrIndicator);
            HeaderList.Add(header.SecondaryCompressorId);
            foreach (byte b in SFIOInteger.Encode(header.CodeTableDataLength))
                HeaderList.Add(b);
            HeaderList.Add(header.NearCacheSize);
            HeaderList.Add(header.SameCacheSize);
        }

        /// <summary>
        /// Visits VCDIFF Window.
        /// </summary>
        /// <param name="window">VCDIFF Window to visit.</param>
        public override void Visit(Window window)
        {
            WindowList.Add(window.WinIndicator);
            foreach (byte b in SFIOInteger.Encode(window.SourceSegmentSize))
                WindowList.Add(b);
            foreach (byte b in SFIOInteger.Encode(window.SourceSegmentPosition))
                WindowList.Add(b);
            foreach (byte b in SFIOInteger.Encode(window.DeltaEncodingLength))
                WindowList.Add(b);
            foreach (byte b in SFIOInteger.Encode(window.TargetWindowSize))
                WindowList.Add(b);
            WindowList.Add(window.DeltaIndicator);
            foreach (byte b in SFIOInteger.Encode(window.AddAndRunDataLength))
                WindowList.Add(b);
            foreach (byte b in SFIOInteger.Encode(window.InstructionsAndSizesLength))
                WindowList.Add(b);
            foreach (byte b in SFIOInteger.Encode(window.CopyAddressesLength))
                WindowList.Add(b);
            foreach (byte b in window.AddAndRunDataSection)
                WindowList.Add(b);
            foreach (byte b in window.InstructionsAndSizesSection)
                WindowList.Add(b);
            foreach (byte b in window.CopyAddressesSection)
                WindowList.Add(b);
        }

        /// <summary>
        /// Visits Add command.
        /// </summary>
        /// <param name="add">Add command to visit.</param>
        public override void Visit(Add add)
        {
            foreach (byte b in add.Sequence)
                AddAndRunDataSection.Add(b);
            InstructionsAndSizesSection.Add(0x01);
            foreach (byte b in SFIOInteger.Encode(add.Size))
                InstructionsAndSizesSection.Add(b);
            InstructionsAndSizesSection.Add(0x00);            
        }

        /// <summary>
        /// Visits Copy command.
        /// </summary>
        /// <param name="copy">Copy command to visit.</param>
        public override void Visit(Copy copy)
        {
            foreach (byte b in SFIOInteger.Encode(copy.Address))
                CopyAddressesSection.Add(b);
            InstructionsAndSizesSection.Add(0x03);
            foreach (byte b in SFIOInteger.Encode(copy.Size))
                InstructionsAndSizesSection.Add(b);
            InstructionsAndSizesSection.Add(0x00);
        }        

        /// <summary>
        /// Visits Noop command.
        /// </summary>
        /// <param name="noop">Noop command to visit.</param>
        public override void Visit(Noop noop)
        {
            InstructionsAndSizesSection.Add(0x00);
            InstructionsAndSizesSection.Add(0x00);
            InstructionsAndSizesSection.Add(0x00);
        }

        /// <summary>
        /// Visits Run command.
        /// </summary>
        /// <param name="run">Run command to visit.</param>
        public override void Visit(Run run)
        {
            AddAndRunDataSection.Add(run.RunByte);
            InstructionsAndSizesSection.Add(0x02);
            foreach (byte b in SFIOInteger.Encode(run.Size))
                InstructionsAndSizesSection.Add(b);
            InstructionsAndSizesSection.Add(0x00);
        }

        /// <summary>
        /// Writes a VCDIFF File at the specified path.
        /// </summary>
        /// <param name="path">Path where the Writer writes the VCDIFF File.</param>
        public void Write(string path)
        {
            File.WriteAllBytes(path, GetBytesTable());
        }


        private byte[] GetBytesTable()
        {
            UInt64 index = 0;
            byte[] bytesTab = new byte[HeaderList.Count + WindowList.Count + AddAndRunDataSection.Count + InstructionsAndSizesSection.Count + CopyAddressesSection.Count];

            foreach (byte b in HeaderList)
            {
                bytesTab[index] = b;
                index++;
            }
            foreach (byte b in WindowList)
            {
                bytesTab[index] = b;
                index++;
            }
            foreach (byte b in AddAndRunDataSection)
            {
                bytesTab[index] = b;
                index++;
            }
            foreach (byte b in InstructionsAndSizesSection)
            {
                bytesTab[index] = b;
                index++;
            }
            foreach (byte b in CopyAddressesSection)
            {
                bytesTab[index] = b;
                index++;
            }

            return bytesTab;
        }

        /// <summary>
        /// Gets a bytes table from a bytes list.
        /// </summary>
        /// <param name="bytesList">Bytes list where we get the bytes for the table.</param>
        /// <returns>The bytes table.</returns>
        public string GetString(IList<byte> bytesList)
        {
            StringBuilder tmp = new StringBuilder();

            foreach (byte b in bytesList)
                tmp.Append(Convert.ToString(b));

            return tmp.ToString();
        }
    }
}
